package com.cn.lp.jpa.cq.domain;

import com.cn.lp.jpa.cq.CQConditionPredicate;
import com.cn.lp.jpa.cq.CQLogicalType;
import org.springframework.util.Assert;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Objects;
import java.util.Optional;

/**
 * 条件块
 *
 * @author wqr
 * Created by on 2019/8/7
 */
class CQConditionBlock<TFrom> {

    /**
     * 上一个条件块
     */
    private CQConditionBlock<TFrom> parent;

    /**
     * 条件
     */
    private CQConditionPredicate<TFrom> condition;

    /**
     * 上一个判断类型 a and ( a or B) logicalType = and
     */
    private CQLogicalType logicalType;

    /**
     * 是否非
     */
    private boolean non;

    private CQConditionBlock() {
    }

    public static <TFrom> CQConditionBlock<TFrom> of() {
        return new CQConditionBlock<>();
    }

    CQConditionBlock<TFrom> beginBlock(CQLogicalType logicalType, boolean non) {
        CQConditionBlock<TFrom> newLayer = of();
        newLayer.parent = this;
        newLayer.logicalType = logicalType;
        this.non = non;
        return newLayer;
    }

    CQConditionBlock<TFrom> endBlock() {
        if (isRoot()) {
            throw new RuntimeException("不能结束跟层");
        }
        toCondition().ifPresent(c -> parent.append(c, logicalType));
        parent.non = false;
        return parent;
    }

    @SuppressWarnings("unchecked")
    void append(CQConditionPredicate<TFrom> condition, CQLogicalType logicalType) {
        if(Objects.isNull(this.condition)) {
            if(non) {
                condition = CombineConditionPredicate.ofNon(condition);
            }
            this.condition = condition;
        } else {
            this.condition = CombineConditionPredicate.of(this.condition, condition, non, logicalType);
        }
    }

    Optional<CQConditionPredicate<TFrom>> toCondition() {
        return Optional.ofNullable(condition);
    }

    public boolean isRoot() {
        return Objects.isNull(parent);
    }

    public CQConditionBlock<TFrom> getRoot() {
        return isRoot() ? this : parent.getRoot();
    }

    private static class CombineConditionPredicate<TFrom> implements CQConditionPredicate<TFrom> {

        CQConditionPredicate<TFrom> c1;
        CQConditionPredicate<TFrom> c2;
        CQLogicalType logicalType;
        boolean non;

        CombineConditionPredicate() {
        }

        public static <TFrom> CombineConditionPredicate ofNon(CQConditionPredicate<TFrom> c1) {
            CombineConditionPredicate result = new CombineConditionPredicate<TFrom>();
            result.c1 = c1;
            Assert.notNull(c1, "previous expression must not be null");
            result.non = true;
            return result;
        }

        public static <TFrom> CombineConditionPredicate of(CQConditionPredicate<TFrom> c1, CQConditionPredicate<TFrom> c2, boolean non, CQLogicalType logicalType) {
            CombineConditionPredicate result = new CombineConditionPredicate<TFrom>();
            result.c1 = c1;
            Assert.notNull(c1, "previous expression must not be null");
            result.c2 = c2;
            Assert.notNull(c1, " following expression must not be null");
            result.logicalType = logicalType;
            result.non = non;
            return result;
        }

        @Override
        public Predicate toPredicate(Root<TFrom> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Predicate p1 = c1.toPredicate(root, query, cb);
            if (c2 == null && non) {
                return cb.not(p1);
            } else {
                Predicate p2 = c2.toPredicate(root, query, cb);
                if (non) {
                    p2 = cb.not(p2);
                }
                return logicalType.combine(p1, p2, cb);
            }

        }
    }


}
